package edu.kit.csl.cratylus.io;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import edu.kit.csl.cratylus.datatypes.Corpus;
import edu.kit.csl.cratylus.datatypes.Sentence;
import edu.kit.csl.cratylus.datatypes.SentencePair;
import edu.kit.csl.cratylus.datatypes.Vocabulary;
import edu.kit.csl.cratylus.datatypes.Word;
import edu.kit.csl.cratylus.ui.Configuration;

/**
 * This class is a concrete factory in the GoF design pattern Abstract Factory.
 * It loads the corpus from PISAs output files.
 */
public class PisaCorpusFactory implements CorpusFactory {
	
	private final String SENTENCE_ID_PATTERN = "Sentence pair \\(([0-9]+)\\)";
	
	private final String SRC_SEN_PATTERN = " \\([{} 0-9]+\\)";
	
	private final String ALIGN_PATTERN = "\\(\\{([ 0-9]+)\\}\\)";
	
	private final Pattern senIdPattern, alignPattern;
	
	/**
	 * Sole constructor.
	 */
	public PisaCorpusFactory() {
		super();
		senIdPattern = Pattern.compile(SENTENCE_ID_PATTERN);
		alignPattern = Pattern.compile(ALIGN_PATTERN);
	}

	/**
	 * Loads the initial Corpus instance from PISAs output files. In order to
	 * specify their location, make sure that alignmentFile points to a *.A.*
	 * file generated by a PISA run.
	 */
	@Override
	public Corpus createCorpus() {
		Corpus corpus = new Corpus();
		try {
			String fileName = Configuration.getSingleton().getString(
					"alignmentFile");
			FileInputStream fstream = new FileInputStream(fileName);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			Logger.getSingleton().notice("Load PISA corpus from " + fileName);
			String line, alignString = "", segString = "";
			String trgtSentence = null;
			int curSenId = 1;
			int curLineRole = 0; // 0: comment, 1: trgt, 2: seg, 3: src
			// Read File Line By Line
			while ((line = br.readLine()) != null) {
				switch (curLineRole) {
				case 0: // We are in the "# Sentence pair (..).." line
					Matcher matcher = senIdPattern.matcher(line);
					if (!matcher.find()) {
						throw new Exception("Malformed GIZA++ file. "
								+ "Could not fetch sentence ID from line "
								+ line);
					}
					int newSenId = Integer.parseInt(matcher.group(1));
					if (newSenId != curSenId) { // New sentence id
						addToCorpus(corpus, trgtSentence, 
								alignString, segString);
						curSenId = newSenId;
					}
					break;
				case 1: // We are in a source language line
					trgtSentence = line;
					break;
				case 2: // we are in a segmentation line
					segString = line;
					break;
				case 3: // we are in a target language line with the alignment
					alignString = line;
					break;
				}
				curLineRole = (curLineRole + 1) % 4;
			}
			addToCorpus(corpus, trgtSentence, 
					alignString, segString); // Add last one
			in.close();
		} catch (Exception e) {// Catch exception if any
			Logger.getSingleton().fatalError("Error while reading PISA "
					+ "alignment file: " + e.toString());
		}
		return corpus;
	}

	/* (non-Javadoc)
	 * Add a new sentence pair to the corpus. The sentences are given in their
	 * string representation (as they appear in the PISA alignment file).
	 * 
	 * @param corpus
	 * @param trgtString
	 * @param alignString PISA target sentence string defining the alignment
	 * @param segString PISA segmentation string defining word boundaries
	 */
	private void addToCorpus(Corpus corpus, String trgtString,
			String alignString, String segString) {
		// Fetch target sentence (phoneme string) and source sentence
		Sentence srcSen = new Sentence(
				alignString.replaceAll(SRC_SEN_PATTERN, ""),
				Vocabulary.getSingleton(Vocabulary.SRC_VOC));
		Sentence trgtSen = createTargetSentence(trgtString,
				segString);
		// Add alignments
		final int m = srcSen.getLength();
		final int n = trgtSen.getLength();
		// Create sentence pair
		SentencePair dataSet = new SentencePair(srcSen, trgtSen,
				createAlignment(alignString, m, n));
		corpus.add(dataSet);
	}
	
	/* (non-Javadoc)
	 * Creates the target language word sequence, using the segmentation line
	 * and the entire phoneme sequence.
	 * 
	 * @param trgtStr phoneme sequence as string (blank separated)
	 * @param segStr segmentation line
	 */
	private Sentence createTargetSentence(String trgtStr, String segStr) {
		String[] segments = segStr.split("f");
		if (segments.length < 1) {
			Logger.getSingleton().error("Malformed segmentation line "
				+ segStr);
			return new Sentence("");
		}
		int lastPos = 0;
		ArrayList<Word> words = new ArrayList<Word>();
		for (int i = 1; i < segments.length - 1; i++) {
			String pronun = trgtStr.substring(lastPos,
					lastPos + segments[i].length() + 1).trim(); // +1 for "f"
			if (pronun.length() > 0) {
				words.add(new Word(pronun));
			}
			lastPos += pronun.length() + 1;
		}
		words.add(new Word(trgtStr.substring(lastPos).trim()));
		return new Sentence(words);
	}

	/* (non-Javadoc)
	 * Creates an alignment instance out of a PISA alignment string
	 * 
	 * @param alignString
	 * @param srcLen length of the source sentence
	 * @param trgtLen length of the target sentence
	 * @return the alignment in integer vector representation
	 */
	private short[] createAlignment(String alignString,
			int srcLen, int trgtLen) {
		short[] alignment = new short[trgtLen]; // like in (Brown et. al., 94)
		// Create alignment array
		Matcher matcher = alignPattern.matcher(alignString);
		for (short srcPos = 0; matcher.find(); srcPos++) {
			for (String trgtPos : matcher.group(1).split(" ")) {
				if (!trgtPos.isEmpty()) {
					int trgtIndex = Integer.parseInt(trgtPos) - 1;
					if (trgtIndex >= trgtLen) {
						Logger.getSingleton().debug("Unknown index " + trgtPos
								+ " in " + alignString);
					} else {
						alignment[Integer.parseInt(trgtPos) - 1] = srcPos;
					}
				}
			}
		}
		return alignment;
	}
}
